﻿using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Security;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Qianke.Core.Common.Decode
{
    public static class RSAHelper
    {
        private static int GetIntegerSize(BinaryReader binr)
        {
            int count;
            var bt = binr.ReadByte();
            if (bt != 0x02)        //expect integer
                return 0;
            bt = binr.ReadByte();


            if (bt == 0x81)
                count = binr.ReadByte();    // data size in next byte
            else
                if (bt == 0x82)
            {
                var highbyte = binr.ReadByte();
                var lowbyte = binr.ReadByte();
                byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
                count = BitConverter.ToInt32(modint, 0);
            }
            else
            {
                count = bt;        // we already have the data size
            }


            while (binr.ReadByte() == 0x00)
            {    //remove high order zeros in data
                count -= 1;
            }
            binr.BaseStream.Seek(-1, SeekOrigin.Current);        //last ReadByte wasn't a removed zero, so back up a byte
            return count;
        }


        private static void EncodeLength(BinaryWriter stream, int length)
        {
            if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
            if (length < 0x80)
            {
                // Short form
                stream.Write((byte)length);
            }
            else
            {
                // Long form
                var temp = length;
                var bytesRequired = 0;
                while (temp > 0)
                {
                    temp >>= 8;
                    bytesRequired++;
                }
                stream.Write((byte)(bytesRequired | 0x80));
                for (var i = bytesRequired - 1; i >= 0; i--)
                {
                    stream.Write((byte)(length >> (8 * i) & 0xff));
                }
            }
        }

        private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
        {
            stream.Write((byte)0x02); // INTEGER
            var prefixZeros = 0;
            for (var i = 0; i < value.Length; i++)
            {
                if (value[i] != 0) break;
                prefixZeros++;
            }
            if (value.Length - prefixZeros == 0)
            {
                EncodeLength(stream, 1);
                stream.Write((byte)0);
            }
            else
            {
                if (forceUnsigned && value[prefixZeros] > 0x7f)
                {
                    // Add a prefix zero to force unsigned if the MSB is 1
                    EncodeLength(stream, value.Length - prefixZeros + 1);
                    stream.Write((byte)0);
                }
                else
                {
                    EncodeLength(stream, value.Length - prefixZeros);
                }
                for (var i = prefixZeros; i < value.Length; i++)
                {
                    stream.Write(value[i]);
                }
            }
        }


        /// <summary>
        /// PEM密匙 转 RSA密匙
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static RSACryptoServiceProvider ConvertPemToRsaKey(string strKey, int keySize = 4096)
        {
            var ms = new MemoryStream(Convert.FromBase64String(strKey));
            var binr = new BinaryReader(ms);
            byte[] MODULUS, E, D, P, Q, DP, DQ, IQ;
            try
            {
                var twobytes = binr.ReadUInt16();
                if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                    binr.ReadByte();        //advance 1 byte
                else if (twobytes == 0x8230)
                    binr.ReadInt16();       //advance 2 bytes
                else
                    return null;


                twobytes = binr.ReadUInt16();
                if (twobytes != 0x0102) //version number
                    return null;
                var bt = binr.ReadByte();
                if (bt != 0x00)
                    return null;

                var elems = GetIntegerSize(binr);
                MODULUS = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                E = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                D = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                P = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                Q = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                DP = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                DQ = binr.ReadBytes(elems);


                elems = GetIntegerSize(binr);
                IQ = binr.ReadBytes(elems);
                // ------- create RSACryptoServiceProvider instance and initialize with public key -----
                var rsa = keySize > 0 ? new RSACryptoServiceProvider(keySize) : new RSACryptoServiceProvider();
                RSAParameters rsAparams = new RSAParameters();
                rsAparams.Modulus = MODULUS;
                rsAparams.Exponent = E;
                rsAparams.D = D;
                rsAparams.P = P;
                rsAparams.Q = Q;
                rsAparams.DP = DP;
                rsAparams.DQ = DQ;
                rsAparams.InverseQ = IQ;
                rsa.ImportParameters(rsAparams);
                return rsa;
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.Message + e.StackTrace);
                return null;
            }
            finally
            {
                binr.Close();
            }
        }

        /// <summary>
        /// RSA解密
        /// </summary>
        /// <param name="privatekey"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public static string Decrypt(string pemKey, string content, int keySize = 4096)
        {
            using (var rsa = ConvertPemToRsaKey(pemKey, keySize))
            {
                byte[] cipherbytes = rsa.Decrypt(Convert.FromBase64String(content), false); //解密
                return Encoding.UTF8.GetString(cipherbytes);//获取数据
            }
        }

        public static byte[] Decrypt_Java(string privateKey,byte[] buffer,int keySize=2048)
        {
            CspParameters cspParameters = new CspParameters();
            cspParameters.Flags = CspProviderFlags.UseMachineKeyStore;
            using (var rSACryptoServiceProvider = new RSACryptoServiceProvider(keySize, cspParameters))
            {
                rSACryptoServiceProvider.FromXmlString(RSAPrivateKeyJava2DotNet(privateKey));
                return rSACryptoServiceProvider.Decrypt(buffer, false);
            }
        }

        public static byte[] Encrypt_Java(string privateKey, byte[] buffer, int keySize = 2048)
        {
            CspParameters cspParameters = new CspParameters();
            cspParameters.Flags = CspProviderFlags.UseMachineKeyStore;
            using (var rSACryptoServiceProvider = new RSACryptoServiceProvider(keySize, cspParameters))
            {
                rSACryptoServiceProvider.FromXmlString(RSAPrivateKeyJava2DotNet(privateKey));
                return rSACryptoServiceProvider.Encrypt(buffer, false);
            }
        }



        /// <summary>
        /// 加密
        /// </summary>
        /// <param name="pemKey"></param>
        /// <param name="content"></param>
        /// <returns></returns>
        public static string Encrypt(string pemKey, string content, int keySize = 4096)
        {
            using (var rsa = ConvertPemToRsaKey(pemKey, keySize))
            {
                byte[] cipherbytes = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false); //加密
                return Convert.ToBase64String(cipherbytes);//获取数据
            }
        }

        /// <summary>
        ///  获取签名数据
        /// </summary>
        /// <param name="pemKey"></param>
        /// <param name="content"></param>
        /// <param name="halg">SHA1|SHA256</param>
        /// <param name="keySize"></param>
        /// <returns></returns>
        public static string SingData(string pemKey, string content, string halg, int keySize = 4096)
        {
            using (var rsa = ConvertPemToRsaKey(pemKey, keySize))
            {
                byte[] cipherbytes = rsa.SignData(Encoding.UTF8.GetBytes(content), halg); //获取签名
                return Convert.ToBase64String(cipherbytes);//获取数据
            }
        }


        public static string RSAPrivateKeyJava2DotNet( string privateKey)
        {
            var privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));
            return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>",
            Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()),
            Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned()));
        }

        /// <summary>
        /// 创建RAS密匙
        /// </summary>
        /// <returns>公匙,私匙</returns>
        public static KeyValuePair<string, string> CreateRSAKey(int keySize = 4096)
        {
            var privateKey = string.Empty;
            var publicKey = string.Empty;
            using (var csp = new RSACryptoServiceProvider(keySize))
            {
                if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp");
                var parameters = csp.ExportParameters(true);
                #region 私匙
                {
                    using (var stream = new MemoryStream())
                    {
                        var writer = new BinaryWriter(stream);
                        writer.Write((byte)0x30); // SEQUENCE
                        using (var innerStream = new MemoryStream())
                        {
                            var innerWriter = new BinaryWriter(innerStream);
                            EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
                            EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
                            EncodeIntegerBigEndian(innerWriter, parameters.D);
                            EncodeIntegerBigEndian(innerWriter, parameters.P);
                            EncodeIntegerBigEndian(innerWriter, parameters.Q);
                            EncodeIntegerBigEndian(innerWriter, parameters.DP);
                            EncodeIntegerBigEndian(innerWriter, parameters.DQ);
                            EncodeIntegerBigEndian(innerWriter, parameters.InverseQ);
                            var length = (int)innerStream.Length;
                            EncodeLength(writer, length);
                            writer.Write(innerStream.GetBuffer(), 0, length);
                        }
                        privateKey = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);
                    }
                }
                #endregion

                #region 公匙
                {
                    using (var stream = new MemoryStream())
                    {

                        var writer = new BinaryWriter(stream);
                        writer.Write((byte)0x30); // SEQUENCE
                        using (var innerStream = new MemoryStream())
                        {
                            var innerWriter = new BinaryWriter(innerStream);
                            EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version
                            EncodeIntegerBigEndian(innerWriter, parameters.Modulus);
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent);
                            //所有参数必须有值设置其他参数值一点点无效数据(保持使用“关键结构参数。指数“值无效数据)
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.D
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.P
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.Q
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.DP
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.DQ
                            EncodeIntegerBigEndian(innerWriter, parameters.Exponent); // instead of parameters.InverseQ
                            var length = (int)innerStream.Length;
                            EncodeLength(writer, length);
                            writer.Write(innerStream.GetBuffer(), 0, length);
                        }
                        publicKey = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length);
                    }
                }
                #endregion
            }

            return new KeyValuePair<string, string>(publicKey, privateKey);
        }



        public static string RSASign(string data, string privateKeyPem, string halg = "SHA1", int keySize = 2048)
        {

            var dataBytes = Encoding.UTF8.GetBytes(data);
            if (keySize == 4096)
            {
                var key = ConvertPemToRsaKey(privateKeyPem, 4096);
                var signatureBytes = key.SignData(dataBytes, halg);
                return Convert.ToBase64String(signatureBytes);
            }
            else
            {
                CspParameters CspParameters = new CspParameters();
                CspParameters.Flags = CspProviderFlags.UseMachineKeyStore;
                using (var privateRsa = new RSACryptoServiceProvider(keySize, CspParameters))
                {
                    privateRsa.FromXmlString(RSAPrivateKeyJava2DotNet(privateKeyPem));
                    byte[] signatureBytes = privateRsa.SignData(dataBytes, halg);
                    return Convert.ToBase64String(signatureBytes);
                }
            }
        }
    }
}
